package com.ubird.tetris.ui;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JPanel;

import com.ubird.particle.Particles;
import com.ubird.tetris.block.TetrisBlock;
import com.ubird.ui.DrawUtil;

public class GamePanel extends JPanel implements KeyListener {

    private static final long serialVersionUID = -2552383962955502906L;

    protected volatile static int FPS = 60;
    protected volatile static long SPF = 1000 / FPS;

    private static final int DATA_EMPTY = 0; // 空数据
    private static final int DATA_BLOCK = 1; // 是方块

    private final int[][] data; // 游戏数据

    private int width;
    private int height;
    private int blockXNum; // 列数
    private int blockYNum; // 行数
    private int gridSize; // 格子大小

    private static final Color FALLING_BLOCK_COLOR = new Color(255, 255, 50);
    TetrisBlock fallingBlock = TetrisBlock.random(); // 正在下落的方块
    TetrisBlock nextBlock = null; // 下一个方块

    public static final int Y_SPEED_INIT = 20;
    public static final int Y_SPEED_ACCE = 15;
    public static final int Y_SPEED_ACCE_MAX = 5;
    private volatile int ySpeed = Y_SPEED_INIT; // 越小、速度越大
    public int counter = 0;

    private ScorePanel scorePanel;

    private int[] disLine; // 可以消除的行，用于做动画特效
    private int disIndex;
    private Composite[] disComposite = new Composite[] { AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f),
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f) };

    private volatile boolean pause = false;
    private int pauseIndex = 0;
    private Color pauseShadowColor = new Color(255, 0, 0);
    private Color pauseFontColor = new Color(255, 200, 200);

    private boolean gameOver = false;
    private int gameOverIndex = 0;

    private Particles ps = Particles.getInstance(200, 10);

    public GamePanel(int blockXNum, int blockYNum, int gridSize, ScorePanel scorePanel) {
        this.blockXNum = blockXNum;
        this.blockYNum = blockYNum;
        this.gridSize = gridSize;

        width = blockXNum * gridSize;
        height = blockYNum * gridSize;
        setPreferredSize(new Dimension(width, height));

        data = new int[blockYNum][blockXNum];
        fallingBlock.init(blockXNum / 2, 0);

        this.scorePanel = scorePanel;
        nextBlock = gererateNextBlock();
        addKeyListener(this);
        startRepaintThread();

        ps = Particles.getInstance(width / 2, height / 2);
    }

    private void restart() {
        fallingBlock = TetrisBlock.random();
        fallingBlock.init(blockXNum / 2, 0);
        scorePanel.clearScore();
        nextBlock = gererateNextBlock();
        disLine = null;
        disIndex = 0;
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                data[i][j] = DATA_EMPTY;
            }
        }
        gameOver = false;
    }

    private void startRepaintThread() {
        new Thread(new Runnable() {
            public void run() {
                try {
                    long start = System.currentTimeMillis();
                    while (true) {
                        Thread.sleep(SPF);
                        start = System.currentTimeMillis() - start;
                        updateData((int) start);
                        start = System.currentTimeMillis();
                        repaint();
                        scorePanel.repaint();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private void updateData(int duration) {
        if (fallingBlock != null) {
            if (isNeedDrawDisLine() || pause || gameOver) { // 正在播消行动画的话，不用更新数据
                return;
            }
            ps.update(duration);
            if (isTime2Update()) {
                if (isCanDown(fallingBlock)) { // 如果可以下落，继续下落
                    fallingBlock.down();
                } else {
                    int[] fixFallingBlock = fixFallingBlock(); // 固定方块
                    disLine = fixFallingBlock;
                }
            }
        }
    }

    private void clearSuccLine(int[] clearLine) {
        for (int i = 0; i < clearLine.length; i++) {
            int to = i == 0 ? 0 : clearLine[i - 1];
            moveDownData(data, clearLine[i], 0);
        }
    }

    /**
     * 将制定数组的数据，从第from-1行到第to行往下移动一行
     * 
     * @param data
     * @param from
     * @param to
     */
    private void moveDownData(int[][] data, int from, int to) {
        if (from <= to)
            throw new IllegalArgumentException("From should be larger than to");
        for (int i = from; i >= to; i--) {
            for (int j = 0; j < data[i].length; j++) {
                data[i][j] = i - 1 >= 0 ? data[i - 1][j] : 0;
            }
        }
    }

    private boolean isTime2Update() {
        if (counter++ == ySpeed) {
            counter = counter % ySpeed;
            return true;
        } else
            return false;
    }

    /**
     * 固定方块
     * 
     * @param fallingBlock
     */
    private int[] fixFallingBlock() {
        int[][] shap = fallingBlock.getShap();
        int x = fallingBlock.getXInWorld();
        int y = fallingBlock.getYInWorld();

        List<Integer> disLineNo = new LinkedList<Integer>(); // 消除的行
        for (int i = 0; i < shap.length; i++) {
            boolean isChange = false;
            for (int j = 0; j < shap[i].length; j++) {
                if (i + y < data.length && j + x >= 0 && j + x < data[i].length) {
                    data[i + y][j + x] |= shap[i][j];
                    isChange = true;
                }
            }
            if (isChange && isDisLine(i + y)) {
                disLineNo.add(i + y);
            }
        }

        fallingBlock = nextBlock;
        fallingBlock.init(blockXNum / 2, 0);
        nextBlock = gererateNextBlock();

        int[] temp = new int[disLineNo.size()];
        if (temp.length > 0)
            Collections.sort(disLineNo);
        int i = 0;
        for (Integer no : disLineNo) {
            temp[i++] = no;
        }
        checkIsGameOver();
        return temp;
    }

    private void checkIsGameOver() {
        for (int i = 0; i < data[0].length; i++) {
            if (data[0][i] == DATA_BLOCK) {
                gameOver();
                break;
            }
        }
    }

    private void gameOver() {
        gameOver = true;
        gameOverIndex = data.length - 1;
        fallingBlock = null;
    }

    private TetrisBlock gererateNextBlock() {
        TetrisBlock random = TetrisBlock.random();
        scorePanel.setNextBlock(random);
        return random;
    }

    /**
     * 指定行是否可以消除
     * 
     * @param line
     * @return
     */
    private boolean isDisLine(int line) {
        for (int i = 0; i < data[line].length; i++) {
            if (data[line][i] == DATA_EMPTY)
                return false;
        }
        return true;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        paintData(g);
        paintFallingBlock(g);
        if (isNeedDrawDisLine()) {
            paintDisLine(g);
        }
        if (pause) {
            paintPause(g);
        }
        if (gameOver) {
            paintGameOver(g);
        }
        ps.draw(g);
    }

    private void paintGameOver(Graphics g) {
        gameOverIndex--;
        gameOverIndex = Math.max(0, gameOverIndex);
    }

    private void paintPause(Graphics g) {
        String pauseInfos = "Press [Enter] or [P] to continue...";
        int length = pauseInfos.length();
        String pauseInfo = pauseInfos;// pauseInfos.substring(0, pauseIndex);
        if (pauseIndex > length / 2)
            DrawUtil.drawShadowString(g, pauseInfo, 18, 50, 100, 3, pauseShadowColor, pauseFontColor);
        pauseIndex = (pauseIndex + 1) % length;
    }

    private void paintDisLine(Graphics g) {
        if (disLine != null) {
            disIndex = (disIndex + 1) % disComposite.length;
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(disComposite[disIndex]);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            for (int i = 0; i < disLine.length; i++) {
                paintDisLine(g2d, disLine[i], disIndex, 4);
            }
            if (disLine.length > 0)
                ps.init(disLine[0] * gridSize);

            g2d.dispose();
            if (disIndex == 0) {
                clearSuccLine(disLine);
                scorePanel.addScore(disLine);
                disLine = null;
            }
        }
    }

    private void paintDisLine(Graphics2D g2d, int lineNo, int disIndex, int thick) {
        for (int i = 0; i < thick; i++) {
            int tempY = lineNo * gridSize + i;
            g2d.draw3DRect(i, tempY, width - i * 2, gridSize - i * 2, false);
        }
    }

    private boolean isNeedDrawDisLine() {
        return disLine != null;
    }

    private void paintFallingBlock(Graphics g) {
        if (fallingBlock != null) {
            g.setColor(FALLING_BLOCK_COLOR);
            fallingBlock.draw(g, gridSize);
        }
    }

    private void paintData(Graphics g) {
        int allCount = data.length * data[0].length;
        int rowCount = data[0].length;

        for (int i = 0; i < data.length; i++) {
            int x = 0;
            int y = i * gridSize;
            for (int j = 0; j < rowCount; j++) {
                x = j * gridSize;
                if (data[i][j] == DATA_EMPTY) {
                    fill3DBlock(g, x, y, calBgColor(50, i, j, rowCount, allCount));
                } else {
                    // fill3DBlock(g, x, y, calBlockColor(255, i, data.length));
                    if (gameOverIndex <= i && gameOver)
                        fill3DBlock(g, x, y, Color.GRAY);
                    else
                        fill3DBlock(g, x, y, calBgColor(250, i, j, rowCount, allCount));
                }
            }
        }
    }

    private void fill3DBlock(Graphics g, int x, int y, Color color) {
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setColor(color);
        g2d.fill3DRect(x, y, gridSize, gridSize, true);
        g2d.dispose();
    }

    private Color calBlockColor(int base, int lineNum, int lineCount) {
        int r = base * lineNum / lineCount;
        return new Color(r, 0, (base - r) / 3);
    }

    private Color calBgColor(int base, int ci, int cj, int rowCount, int allCount) {
        int b = base * (ci * rowCount + cj) / allCount;
        int r = base * cj / rowCount;
        return new Color(r, base - b, b);
    }

    @Override
    public void keyTyped(KeyEvent e) {
    }

    private void switchPause() {
        pause = !pause;
    }

    @Override
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
        case KeyEvent.VK_DOWN:
        case KeyEvent.VK_SPACE:
        case KeyEvent.VK_S:
            if (isCanControl() && isCanDown(fallingBlock))
                fallingBlock.down();
            break;
        case KeyEvent.VK_UP:
        case KeyEvent.VK_W:
            if (isCanControl() && isCanRotate(fallingBlock))
                fallingBlock.rotate();
            break;
        case KeyEvent.VK_LEFT:
        case KeyEvent.VK_A:
            if (isCanControl() && isCanLeft(fallingBlock)) {
                fallingBlock.left();
            }
            break;
        case KeyEvent.VK_RIGHT:
        case KeyEvent.VK_D:
            if (isCanControl() && isCanRight(fallingBlock)) {
                fallingBlock.right();
            }
            break;
        case KeyEvent.VK_P:
        case KeyEvent.VK_ENTER:
            switchPause();
            break;
        case KeyEvent.VK_ESCAPE:
            restart();
        default:
            break;
        }
    }

    private boolean isCanControl() {
        return !pause && !gameOver;
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    private boolean isValid(TetrisBlock newBlock) {
        int[][] shap = newBlock.getShap();
        int x = newBlock.getXInWorld();
        int y = newBlock.getYInWorld();
        for (int i = 0; i < shap.length; i++) {
            for (int j = 0; j < shap[i].length; j++) {
                if ((shap[i][j] == 1 && (i + y >= data.length || // 有某一格超过了下边界
                        j + x < 0 || // 有某一格超过了左边界
                        j + x >= data[i].length // 有某一格超过了右边界
                )))
                    return false;
                if ((i + y < data.length && j + x >= 0 && j + x < data[i].length)
                        && (shap[i][j] & data[i + y][j + x]) == DATA_BLOCK) // 和其它重叠
                    return false;
            }
        }
        return true;
    }

    private boolean isCanRotate(TetrisBlock block) {
        return isValid(block.copyRotate());
    }

    private boolean isCanDown(TetrisBlock block) {
        return isValid(block.copyDown());
    }

    private boolean isCanRight(TetrisBlock block) {
        return isValid(block.copyRight());
    }

    private boolean isCanLeft(TetrisBlock block) {
        return isValid(block.copyLeft());
    }
}